iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
Software Development

從零打造邊緣運算閘道器:Raspberry Pi 與 Linux 底層軟硬整合實戰系列 第 13

Day 13 - 完美點火:如何寫出一支跨硬體 (Pi 3 / Pi 4) 的自動 UDC 綁定腳本?

  • 分享至 

  • xImage
  •  

NOTE

本系列文章已進行全域去敏與匿名化處理,專案代號統一為 DataGateway,內網 IP 統一為 10.0.0.x。

在前面的幾天,我們已經成功地在 ConfigFS 中設定好了 USB Gadget 的各項參數,包含把樹莓派變成一顆具有 Mass Storage (隨身碟) 以及 CDC ACM (虛擬序列埠) 功能的複合設備 (Composite Device)。

但是,設定好 ConfigFS 的資料夾結構,就代表 USB 已經啟動了嗎? 還沒。

要讓這一切真正「通電」,我們還差最後一個關鍵步驟:將設定好的 Gadget 綁定到硬體的 UDC (USB Device Controller)。

問題情境:為什麼腳本換台機器就掛了?
一開始,我在網路上查到的 UDC 綁定教學通常長這樣:

bash

# 綁定 UDC
echo 20980000.usb > /sys/kernel/config/usb_gadget/my_gadget/UDC

這行指令在 Raspberry Pi Zero 或 Pi 1 上運作得非常完美。但當我把同一支 Shell Script 丟到 Raspberry Pi 4 上執行時,系統卻毫不留情地吐出了 No such file or directory 或 Write error: No such device 的錯誤。

為什麼? 因為不同世代的樹莓派,其底層 USB 控制器的硬體位址(在 Device Tree 中的名稱)根本不一樣!

Pi Zero / Pi 1 / Pi 3 (部分模式):UDC 名稱通常是 20980000.usb 或 3f980000.usb。
Pi 4:UDC 名稱變成了 fe980000.usb。
如果我們的 DataGateway 產品需要同時相容於多種 Raspberry Pi 型號,總不能每次佈署前都手動去改 Script 吧?

錯誤嘗試:硬幹判斷式
起初,我試著用 cat /proc/cpuinfo 來判斷硬體版本,然後寫一長串的 if-else:

bash

# 錯誤示範:難以維護的寫法
PI_MODEL=$(cat /sys/firmware/devicetree/base/model)
if [[ "$PI_MODEL" == *"Raspberry Pi 4"* ]]; then
    echo "fe980000.usb" > UDC
elif [[ "$PI_MODEL" == *"Raspberry Pi 3"* ]]; then
    echo "3f980000.usb" > UDC
else
    echo "20980000.usb" > UDC
fi

這寫法不僅醜陋,而且非常脆弱。如果作業系統升級,或者未來出了 Raspberry Pi 5,這段程式碼立刻就會失效。更不用說,有些系統的 UDC 驅動模組可能叫做 dwc2,名稱又會有所不同。

底層原理:/sys/class/udc 的秘密
在 Linux 核心中,所有的硬體設備都有對應的 sysfs 節點。既然系統知道現在哪個 UDC 處於可用狀態,我們何不直接問系統呢?

Linux 會將當前載入的 UDC 註冊在 /sys/class/udc/ 目錄下。不管你的硬體是 Pi 3 還是 Pi 4,只要 dwc2 (USB OTG 驅動) 有正確掛載,這個目錄下就會出現一個對應的 UDC 資料夾。

你可以自己試試看:

bash

ls /sys/class/udc/
# 在 Pi 4 上會輸出:fe980000.usb

最終解決方案:動態獲取 UDC 名稱
既然如此,我們只需要去讀取 /sys/class/udc/ 目錄下的檔名,然後動態寫入到 ConfigFS 的 UDC 檔案中即可。這樣一來,腳本就具備了 跨硬體相容性!

以下是我們 DataGateway 最終在產線穩定運行的「完美點火」腳本片段:

bash

# ----------------------------------------
# 完美點火:動態綁定 UDC
# ----------------------------------------
# 1. 檢查系統中是否有可用的 UDC
UDC_DIR="/sys/class/udc"
if [ ! -d "$UDC_DIR" ] || [ -z "$(ls -A $UDC_DIR)" ]; then
    echo "[ERROR] 找不到可用的 UDC!請確認是否已載入 dwc2 模組 (dtoverlay=dwc2)。"
    exit 1
fi
# 2. 自動抓取第一個可用的 UDC 名稱
UDC_NAME=$(ls $UDC_DIR | head -n 1)
echo "[INFO] 偵測到 UDC 控制器: ${UDC_NAME}"
# 3. 執行綁定,通電!
# 假設我們在 /sys/kernel/config/usb_gadget/gateway_gadget 裡面
echo "${UDC_NAME}" > UDC
if [ $? -eq 0 ]; then
    echo "[SUCCESS] USB Gadget 啟動成功!"
else
    echo "[ERROR] USB Gadget 綁定失敗。"
    exit 1
fi

TIP

反向操作 (卸載) 如果你要修改 ConfigFS 的設定,必須先「斷電」。斷電的方法很簡單,就是對 UDC 寫入空字串: echo "" > UDC 斷電後,你才能刪除或修改 ConfigFS 裡面的 endpoint 參數喔!

透過這短短幾行程式碼,我們的初始化腳本 (Provisioning Script) 再也不用管它是在 Pi 3 還是 Pi 4 上執行了。這種「向系統詢問能力」而非「硬寫硬體位址」的設計思維,正是開發穩定邊緣運算設備的關鍵。

明天,我們將進入第三階段:檔案系統的生存遊戲。我們要在這台「隨身碟」裡面,憑空捏出一個 FAT32 的虛擬磁碟!


上一篇
Day 12 - 複合設備 (Composite) 的惡夢:Windows 驅動與 Symlink 順序的黃金法則
下一篇
Day 14 - 虛擬磁碟的誕生:如何用 dd 與 mkdosfs 憑空捏出一個 FAT32 空間?
系列文
從零打造邊緣運算閘道器:Raspberry Pi 與 Linux 底層軟硬整合實戰26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言